/*
 * Copyright (c) 2021 hpmicro
 *
 * SPDX-License-Identifier: BSD-3-Clause
 *
 */
#include "boot_user.h"
#include "ota_api.h"
#include "hpm_romapi.h"
#include "hpm_ppor_drv.h"
#include "hpm_l1c_drv.h"
#include "ota_verify.h"
//#include "hpm_gpio_drv.h"


#define BLOCK_SIZE     (64*1024)

static uint32_t current_addr = 0;
static uint8_t sector_buffer[SECTOR_SIZE];
static xpi_nor_config_t s_xpi_nor_config;
static user_fota_header_t current_fota_header;
// static uint32_t flash_size;
// static uint32_t sector_size;
// static uint32_t block_size;
// static uint32_t page_size;

void ota_board_flash_init(void)
{
    xpi_nor_config_option_t option;
    option.header.U = BOARD_APP_XPI_NOR_CFG_OPT_HDR;
    option.option0.U = BOARD_APP_XPI_NOR_CFG_OPT_OPT0;
    option.option1.U = BOARD_APP_XPI_NOR_CFG_OPT_OPT1;
    disable_global_irq(CSR_MSTATUS_MIE_MASK);
    rom_xpi_nor_auto_config(BOARD_APP_XPI_NOR_XPI_BASE, &s_xpi_nor_config, &option);
    enable_global_irq(CSR_MSTATUS_MIE_MASK);

    // rom_xpi_nor_get_property(BOARD_APP_XPI_NOR_XPI_BASE, &s_xpi_nor_config, xpi_nor_property_total_size,
    //     &flash_size);
    // rom_xpi_nor_get_property(BOARD_APP_XPI_NOR_XPI_BASE, &s_xpi_nor_config, xpi_nor_property_sector_size,
    //     &sector_size);
    // rom_xpi_nor_get_property(BOARD_APP_XPI_NOR_XPI_BASE, &s_xpi_nor_config, xpi_nor_property_block_size,
    //     &block_size);
    // rom_xpi_nor_get_property(BOARD_APP_XPI_NOR_XPI_BASE, &s_xpi_nor_config, xpi_nor_property_page_size, &page_size);

    // printf("Flash Size:%dMBytes\nFlash Sector Size:%dKBytes\nFlash Block Size:%dKBytes\nFlash Page Size:%dBytes\n",
    //     flash_size / 1024U / 1024U, sector_size / 1024U, block_size / 1024U, page_size);
}

void ota_board_complete_reset(void)
{
    hpm_stat_t status;

    if (current_addr != FLASH_USER_APP1_ADDR && current_addr != FLASH_USER_APP2_ADDR)
        return;

    if (!ota_board_auto_checksum())
    {
        printf("BAD! verify failed!\r\n");
        return;
    }

    disable_global_irq(CSR_MSTATUS_MIE_MASK);
    status = rom_xpi_nor_erase(BOARD_APP_XPI_NOR_XPI_BASE, xpi_xfer_channel_auto, &s_xpi_nor_config,
        (current_addr == FLASH_USER_APP1_ADDR) ? (FLASH_USER_APP2_ADDR - FLASH_ADDR_BASE) : (FLASH_USER_APP1_ADDR - FLASH_ADDR_BASE),
        SECTOR_SIZE);
    enable_global_irq(CSR_MSTATUS_MIE_MASK);

    current_addr = 0;
    printf("ota success!\n");
    ppor_sw_reset(HPM_PPOR, 10);
}

void ota_board_flash_read(uint32_t addr, void* buffer, uint32_t len)
{
    rom_xpi_nor_read(BOARD_APP_XPI_NOR_XPI_BASE, xpi_xfer_channel_auto, &s_xpi_nor_config, buffer, addr, len);
}

uint32_t ota_board_flash_size(uint8_t ota_index)
{
    return ota_index == 0 ? FLASH_USER_APP1_SIZE : FLASH_USER_APP2_SIZE;
}

void ota_board_flash_erase(uint8_t ota_index)
{
    hpm_stat_t status;
    uint32_t addr, user_size, start_index, erase_count = 0;
    if (ota_index == 0)
    {
        addr = FLASH_USER_APP1_ADDR - FLASH_ADDR_BASE;
        user_size = SECTOR_SIZE;//FLASH_USER_APP1_SIZE;
    }
    else if (ota_index == 1)
    {
        addr = FLASH_USER_APP2_ADDR - FLASH_ADDR_BASE;
        user_size = SECTOR_SIZE;//FLASH_USER_APP2_SIZE;
    }
    else
    {
        return;
    }

    if (!(addr % BLOCK_SIZE) && !(user_size % BLOCK_SIZE))
    {
        printf("erase block mode!\n");
        start_index = addr;
        erase_count = addr + user_size;
        disable_global_irq(CSR_MSTATUS_MIE_MASK);
        for (start_index; start_index < erase_count;)
        {
            status = rom_xpi_nor_erase_block(BOARD_APP_XPI_NOR_XPI_BASE, xpi_xfer_channel_auto, &s_xpi_nor_config, start_index);
            if (status != status_success)
            {
                printf("ERROR:erase block failed:%d\n", start_index);
                break;
            }
            start_index += BLOCK_SIZE;
        }
        enable_global_irq(CSR_MSTATUS_MIE_MASK);
        printf("erase block success!\n");
    }
    else if (!(addr % SECTOR_SIZE) && !(user_size % SECTOR_SIZE))
    {
        //printf("erase sector mode!\n");
        start_index = addr;
        erase_count = addr + user_size;
        //printf("start_index:%d, erase_count:%d\n", start_index, erase_count);
        disable_global_irq(CSR_MSTATUS_MIE_MASK);
        for (start_index; start_index < erase_count;)
        {
            status = rom_xpi_nor_erase_sector(BOARD_APP_XPI_NOR_XPI_BASE, xpi_xfer_channel_auto, &s_xpi_nor_config, start_index);
            if (status != status_success)
            {
                printf("ERROR:erase sector failed:%d\n", start_index);
                break;
            }
            start_index += SECTOR_SIZE;
        }
        enable_global_irq(CSR_MSTATUS_MIE_MASK);
        //printf("erase sector success!\n");
    }
    else
    {
        printf("ERROR: flase erase not algin!\n");
        return;
    }
}

void ota_board_flash_write(uint32_t addr, void const* src, uint32_t len)
{
    hpm_stat_t status;
    uint32_t write_addr = addr - FLASH_ADDR_BASE;
    static uint32_t erase_addr = 0;
    if (len > SECTOR_SIZE)
    {
        printf("ERROR: write size overflow!\n");
        return;
    }
    memcpy(sector_buffer, src, len);
    if (erase_addr <= (write_addr + len))
    {
        if (erase_addr == 0 && !(write_addr % SECTOR_SIZE))
        {
            erase_addr = write_addr;
        }
        // printf("write addr:0x%08x, erase_addr:0x%08x\n", write_addr, erase_addr);
        disable_global_irq(CSR_MSTATUS_MIE_MASK);
        status = rom_xpi_nor_erase_sector(BOARD_APP_XPI_NOR_XPI_BASE, xpi_xfer_channel_auto, &s_xpi_nor_config, erase_addr);
        enable_global_irq(CSR_MSTATUS_MIE_MASK);
        if (status != status_success)
        {
            printf("ERROR:erase sector failed, addr:0x%x\n", erase_addr);
            return;
        }
        erase_addr += SECTOR_SIZE;
    }

    disable_global_irq(CSR_MSTATUS_MIE_MASK);
    status = rom_xpi_nor_program(BOARD_APP_XPI_NOR_XPI_BASE, xpi_xfer_channel_auto, &s_xpi_nor_config,
        (uint32_t*)sector_buffer, write_addr, len);
    enable_global_irq(CSR_MSTATUS_MIE_MASK);
    if (status != status_success)
    {
        printf("ERROR:program failed: status = %ld!\r\n", status);
        return;
    }
    // printf("write success!\n");
}

void ota_board_header_write(uint8_t ota_index, user_fota_header_t* fota_header)
{
    hpm_stat_t status;
    uint32_t write_addr;

    if (fota_header == NULL || sizeof(user_fota_header_t) > SECTOR_SIZE)
        return;
    if (ota_index == 0)
        /*write_addr = FLASH_APP1_HEADER_ADDR - FLASH_ADDR_BASE;*/
        printf("never be here", write_addr);
    else if (ota_index == 1)
        write_addr = FLASH_APP2_HEADER_ADDR - FLASH_ADDR_BASE;
    else
        return;

    if (write_addr % SECTOR_SIZE)
        return;

    // printf("header write addr:0x%08x\n", write_addr);
    disable_global_irq(CSR_MSTATUS_MIE_MASK);
    status = rom_xpi_nor_erase_sector(BOARD_APP_XPI_NOR_XPI_BASE, xpi_xfer_channel_auto, &s_xpi_nor_config, write_addr);
    enable_global_irq(CSR_MSTATUS_MIE_MASK);
    if (status != status_success)
    {
        printf("ERROR:erase sector failed, addr:0x%x\n", write_addr);
        return;
    }

    disable_global_irq(CSR_MSTATUS_MIE_MASK);
    status = rom_xpi_nor_program(BOARD_APP_XPI_NOR_XPI_BASE, xpi_xfer_channel_auto, &s_xpi_nor_config,
        (uint32_t*)fota_header, write_addr, sizeof(user_fota_header_t));
    enable_global_irq(CSR_MSTATUS_MIE_MASK);
    if (status != status_success)
    {
        printf("ERROR:program failed: status = %ld!\r\n", status);
        return;
    }
    // printf("write success!\n");
}

int ota_fota_flash_checksum(uint32_t addr, uint32_t len, uint32_t* checksum)
{
    hpm_stat_t status;
    int i, j;
    int allsize = 0;
    int read_len;
    unsigned int tmp;
    unsigned char buf[512];

    *checksum = 0;
    while (1)
    {
        if (len - allsize > sizeof(buf))
            read_len = sizeof(buf);
        else
            read_len = len - allsize;

        status = rom_xpi_nor_read(BOARD_APP_XPI_NOR_XPI_BASE, xpi_xfer_channel_auto, &s_xpi_nor_config, (uint32_t *)buf, \
            addr + allsize - FLASH_ADDR_BASE, read_len);
        if (status != status_success)
        {
            printf("flash read fail\r\n");
            return -1;
        }
        for (i = 0; i < read_len; i++)
        {
            tmp = buf[i];
            *checksum += tmp;
        }
        allsize += read_len;

        if (allsize >= len)
            return 0;
    }
    return -1;
}

bool ota_board_auto_verify(uint32_t addr, user_fota_header_t* fota_header)
{
    uint8_t* xip_point = (uint8_t*)addr;
    uint8_t* result_verify = NULL;
    uint8_t hash_len = 0;

    if(fota_header == NULL)
        return false;
    if(ota_hash_init(fota_header->hash_type) != 0)
        return false;

    if(ota_hash_update(xip_point, fota_header->len) != 0)
    {
        printf("hash update fail\r\n");
        return false;
    }

    result_verify = ota_hash_finsh();
    if(result_verify == NULL)
        return false;

    if(fota_header->hash_type == OTA_HASH_TYPE_CHECKSUM || \
       fota_header->hash_type == OTA_HASH_TYPE_XOR || \
       fota_header->hash_type == OTA_HASH_TYPE_CRC32)
    {
        hash_len = 4;
    }
    else if(fota_header->hash_type == OTA_HASH_TYPE_SHA1)
    {
        hash_len = 20;
    }
    else
    {
        hash_len = 32;
    }

    if(memcmp(fota_header->hash_data, result_verify, hash_len) != 0)
        return false;

    return true;
}

bool ota_board_auto_checksum(void)
{
    return ota_board_auto_verify(current_addr, &current_fota_header);
}

void ota_board_auto_write(void const* src, uint32_t len)
{
    static uint32_t offset = 0;
    uint32_t checksum;
    int ret = -1;
    user_fota_header_t* current_header = (user_fota_header_t*)src;
    if (current_header->magic == USER_UPGREAD_FLAG_MAGIC && \
        current_header->device == BOARD_DEVICE_ID)
    {
        if (((uint32_t*)src)[sizeof(user_fota_header_t) / sizeof(uint32_t)] == BOOT_USER_APP2_SIGNATURE)
        {
            memcpy(&current_fota_header, current_header, sizeof(user_fota_header_t));
            printf("ota1, length:%d,type:0x%08x\n", current_header->len, current_header->hash_type);
            ota_board_header_write(1, &current_fota_header);
            current_addr = FLASH_USER_APP2_ADDR;
            offset = 0;
            src = src + sizeof(user_fota_header_t);
            len -= sizeof(user_fota_header_t);
            printf("ota data download...\n");
        }
    }

    if (current_addr != 0)
    {
        ota_board_flash_write(current_addr + offset, src, len);
        offset += len;
        if (offset >= current_fota_header.len)
        {
            //complete checksum and reset
            // printf("complete checksum and reset!\r\n");
            //ota_board_complete_reset();
            offset = 0;
        }
    }
}

static void ota_board_app_checkerase(uint8_t ota_index)
{
    if (ota_index == 0)
    {
        if (!ota_board_app_valid(FLASH_USER_APP1_ADDR, BOOT_USER_APP2_SIGNATURE))
        {
            return;
        }
    }
    else if (ota_index == 1)
    {
        if (!ota_board_app_valid(FLASH_USER_APP2_ADDR, BOOT_USER_APP2_SIGNATURE))
        {
            return;
        }
    }
    else
    {
        return;
    }
    ota_board_flash_init();
    disable_global_irq(CSR_MSTATUS_MIE_MASK);
    rom_xpi_nor_erase(BOARD_APP_XPI_NOR_XPI_BASE, xpi_xfer_channel_auto, &s_xpi_nor_config,
        (ota_index == 0) ? (FLASH_USER_APP2_ADDR - FLASH_ADDR_BASE) : (FLASH_USER_APP1_ADDR - FLASH_ADDR_BASE),
        SECTOR_SIZE);
    enable_global_irq(CSR_MSTATUS_MIE_MASK);
}
    

void ota_board_app_jump(uint8_t ota_index)
{
    ota_board_app_checkerase(ota_index);
    if (ota_index == 0)
    {   
        //printf("Jump to application @0x%x(0x%x)\r\n", (FLASH_USER_APP1_ADDR + 4), *(volatile uint32_t*)(FLASH_USER_APP1_ADDR + 4));
        l1c_dc_writeback_all();
        l1c_dc_disable();
        l1c_ic_disable();
        fencei();
        __asm("la a0, %0" ::"i"(FLASH_USER_APP1_ADDR + 16));
        __asm("jr a0");
        while (1);
    }
    else if(ota_index == 1)
    {
        //printf("Jump to application @0x%x(0x%x)\r\n", (FLASH_USER_APP2_ADDR + 4), *(volatile uint32_t*)(FLASH_USER_APP2_ADDR + 4));
        l1c_dc_writeback_all();
        l1c_dc_disable();
        l1c_ic_disable();
        fencei();
        __asm("la a0, %0" ::"i"(FLASH_USER_APP2_ADDR + 16));
        __asm("jr a0");
        while (1);
    }
}

bool ota_board_app_valid(uint32_t app_addr, uint32_t signal_magic)
{
    //printf("addr_value:0x%08X, signal_magic:0x%08X\n", ((volatile uint32_t const*)app_addr)[0], signal_magic);
    if (((volatile uint32_t const*)app_addr)[0] != signal_magic)
    {
        return false;
    }

    return true;
}


uint32_t get_board_app_version(uint32_t app_addr)
{
    return ((volatile uint32_t const*)app_addr)[1];
}